package com.edianping.thread;

import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;

import org.apache.log4j.Logger;

import com.daguu.lib.httpsqs4j.HttpsqsClient;
import com.daguu.lib.httpsqs4j.HttpsqsStatus;
import com.edianping.config.ProcessThreadConfigBean;
import com.edianping.store.ShopDBStore;
import com.edianping.store.ShopStatisticDBStore;
import com.edianping.util.TaskType;

/**
 * Shop info process, which including: tag, feature ids, pay method ids update
 * these field in shop_info according to the newly added review content
 * 
 * @author jensen
 * 
 */
public class ShopInfoProcessThread implements Runnable {
	HttpsqsClient client = null;
	private ProcessThreadConfigBean config;
	private boolean isrunning = true;

	private static Logger log = Logger.getLogger(ShopInfoProcessThread.class);

	public ShopInfoProcessThread(HttpsqsClient client,
			ProcessThreadConfigBean config) {
		this.client = client;
		this.config = config;
	}

	public void run() {
		try {
			log.info("Start " + Thread.currentThread().getName());

			while (isrunning) {
				HttpsqsStatus status = client
						.getStatus(TaskType.SHOP_INFO_STATISITC);
				if (status.unreadNumber > 0) {
					// get all shop_ids, and merge them
					// put all shop info to set
					Set<String> dataSet = new HashSet<String>();
					for (int i = 0; i < status.unreadNumber; i++) {
						String strData = client
								.getString(TaskType.SHOP_INFO_STATISITC);
						log.info(Thread.currentThread().getName()
								+ ": get content, " + strData);
						dataSet.add(strData);
					}

					// merge detail content
					Map<Integer, String> tempMap = new HashMap<Integer, String>();
					for (String strData : dataSet) {
						int sid = Integer.parseInt(strData.substring(0,
								strData.indexOf("|")).trim());
						if (tempMap.containsKey(sid)) {
							tempMap.put(sid, mergeContent(tempMap.get(sid), strData));
						} else {
							tempMap.put(sid, strData);
						}
					}

					// foreach shop_id, update its score, and update to database
					for (String strData : tempMap.values()) {
						log.info(Thread.currentThread().getName() + ", content to process: " + strData);

						// parse string data, string data is in the following
						// format
						// shop_id|tagname tagname tagname|paymethodid
						// paymethodid|featureid featureid
						String[] contents = strData.split("\\|");
						int shopId = Integer.parseInt(contents[0].trim());
						// get statistic information from database
						String[] dbData = ShopDBStore.GetShopInfoStatisticById(shopId);
						String[] dbDataStatistic = ShopStatisticDBStore.GetShopInfoStatisticById(shopId);
						String[] compareResult = new String[3];

						// compare the two value
						String tags = contents[1];
						// split tags
						String[] genTag = generateCountString(tags, dbDataStatistic[0], " ");
						compareResult[0] = genTag[0];

						String paymethods = contents[2];
						// split paymethod
						String[] genPaymethods = generateCountString(paymethods, dbDataStatistic[1], ",");
						compareResult[1] = genPaymethods[0];

						String features = contents[3];
						// split feature
						String[] genFeature = generateCountString(features, dbDataStatistic[2], ",");
						compareResult[2] = genFeature[0];

						// if there is difference, update to database
						if (needUpdate(compareResult, dbData)) {
							log.info(Thread.currentThread().getName()
									+ ": shop " + shopId + " need to update");
							ShopDBStore.UpdateTagPaymethodFeature(shopId, compareResult[0], compareResult[1], compareResult[2]);

							// also update shop_info_statistic table
							// calculate each tag's count, append to the
							// original tag string in shop_statistic table
							ShopStatisticDBStore.UpdateTagPaymethodFeature( shopId, genTag[1], genPaymethods[1], genFeature[1]);
						} else {
							log.info(Thread.currentThread().getName()
									+ ": shop " + shopId
									+ " does not need to update");
						}
					}
				} else {
					log.info(Thread.currentThread().getName()
							+ ": no item to process");
					Thread.sleep(this.config.getSleepperiod());
				}
			}
		} catch (Exception e) {
			log.error(Thread.currentThread().getName() + " " + e.getMessage());
			e.printStackTrace();
		}
	}

	private String mergeContent(String orignal, String strData) {
		String result = "";

		String[] orignalContents = orignal.split("\\|");
		String[] contents = strData.split("\\|");

		String tag = compareContent(orignalContents[1], contents[1], " ");
		String paymethod = compareContent(orignalContents[2], contents[2], ",");
		String feature = compareContent(orignalContents[3], contents[3], ",");

		if (tag.equalsIgnoreCase(""))
			tag = " ";
		if (paymethod.equalsIgnoreCase(""))
			paymethod = " ";
		if (feature.equalsIgnoreCase(""))
			feature = " ";

		result = orignalContents[0] + "|" + tag + "|" + paymethod + "|"
				+ feature;

		return result;
	}

	/**
	 * This method is used to compare whether the newly added info existing in
	 * database content
	 * 
	 * separate can be ',' or ' '
	 * 
	 * @param newly
	 * @param existing
	 * @param sep
	 * @return
	 */
	private String compareContent(String newly, String existing, String sep) {
		if (existing.equalsIgnoreCase("")) {
			return newly;
		}

		String result = "";

		String[] exists = existing.trim().split(sep);
		String[] news = newly.trim().split(sep);
		Set<String> tempSet = new HashSet<String>();

		for (String str : news) {
			if (!str.equalsIgnoreCase(""))
				tempSet.add(str);
		}
		for (String str : exists) {
			if (!str.equalsIgnoreCase(""))
				tempSet.add(str);
		}

		boolean isFirst = true;
		for (String str : tempSet) {
			if (isFirst) {
				isFirst = false;
				result += str;
			} else {
				result += sep + str;
			}
		}

		return result;
	}

	/**
	 * this function go through the string exist in database if match tag found,
	 * update the tag number
	 * 
	 * first result is an array without count second result is an array with
	 * count
	 */
	private String[] generateCountString(String newly, String existing, String sep) {
		String[] result = { "", "" };
		String[] exists = null;
		String[] news = null;
		Map<String, Integer> tempMap = new HashMap<String, Integer>();
		
		//parameter validation
		if( !existing.equalsIgnoreCase("") ){
			exists = existing.trim().split(sep);
		}
		
		if( !newly.equalsIgnoreCase("") ){
			news = newly.trim().split(sep);
		}

		// parse original string to map
		if( exists != null ){
			for (String str : exists) {
				if (str.indexOf(":") == -1) {
					log.error(Thread.currentThread().getName()
							+ ": invalid string format:" + str);
				} else {
					String[] tagNameNum = str.split(":");
					tempMap.put(tagNameNum[0], Integer.parseInt(tagNameNum[1]));
				}
			}
		}
		// update map according to newly added string
		if( news != null ){
			for (String str : news) {
				if (!str.equalsIgnoreCase("")) {
					if (tempMap.containsKey(str)) {
						int count = tempMap.get(str);
						tempMap.put(str, count + 1);
					} else {
						tempMap.put(str, 1);
					}
				}
			}
		}
		// sort map
		if( tempMap.size() > 0 ){
			ValueComparator bvc =  new ValueComparator(tempMap);
	        TreeMap<String,Integer> sortedMap = new TreeMap(bvc);
	        sortedMap.putAll(tempMap);
	        
			// assemble string
			boolean isFirst = true;
			for (Map.Entry<String, Integer> e : sortedMap.entrySet()) {
				if (isFirst) {
					result[0] += e.getKey();
					result[1] += e.getKey() + ":" + e.getValue();
					isFirst = false;
				} else {
					result[0] += sep + e.getKey();
					result[1] += sep + e.getKey() + ":" + e.getValue();
				}
			}
		}
		return result;
	}

	private boolean needUpdate(String[] compareResult, String[] dbData) {
		boolean result = false;

		if (!compareResult[0].equalsIgnoreCase(dbData[0])
				|| !compareResult[0].equalsIgnoreCase(dbData[1])
				|| !compareResult[0].equalsIgnoreCase(dbData[2])) {
			result = true;
		}

		return result;
	}
}

/**
 * subclass for map value compare
 * 
 * @author jensen
 *
 * @param <V>
 */
class ValueComparator<V> implements Comparator<V> {

	Map<String,V> base;

	public ValueComparator(Map<String,V> base) {
		this.base = base;
	}

	public int compare(Object a, Object b) {

		if((Integer)base.get(a) < (Integer)base.get(b)) {
			return 1;
		} else if ((Integer) base.get(a) == (Integer) base.get(b)) {
			return 1;
		} else {
			return -1;
		}
	}
}
